package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.asset.TextureKey;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.PlaneCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Plane;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Quad;
import com.jme3.scene.shape.Sphere;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import mygame.Units.Ally;
import mygame.Units.Enemy;
import mygame.Units.EnemyBallon;
import mygame.Units.Player;

/**
 * test
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private BulletAppState bulletAppState;
    public static final Quaternion ROLL090  = new Quaternion().fromAngleAxis(FastMath.PI/2,   new Vector3f(0,1,0));
    public static final Quaternion YAW090   = new Quaternion().fromAngleAxis(FastMath.PI/2,   new Vector3f(0,1,0));
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    Node shootables = new Node("shootables");
    ParticleEmitter debris;
    Geometry wind,wind_throw,wind_start;
    float windanimation=1000;
    
    
    
    @Override
    public void simpleInitApp() {
        Logger.getLogger("").setLevel(Level.SEVERE);
        initLight();
        initKeys();
        rootNode.attachChild(shootables);
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
        
        //gizmo
        Arrow arrow = new Arrow(Vector3f.UNIT_X);
        arrow.setLineWidth(4);
        putShape(arrow,assetManager, ColorRGBA.Red,rootNode);
        arrow = new Arrow(Vector3f.UNIT_Y);
        arrow.setLineWidth(4);
        putShape(arrow,assetManager, ColorRGBA.Green,rootNode);
        arrow = new Arrow(Vector3f.UNIT_Z);
        arrow.setLineWidth(4);
        putShape(arrow,assetManager, ColorRGBA.Blue,rootNode);
        
        flyCam.setDragToRotate(true);
        flyCam.setEnabled(false);
        
        //boat
        World.player=new Player();
        World.player.build(assetManager, getPhysicsSpace(), rootNode, shootables);
        Enemy enemy= new EnemyBallon();
        enemy.build(assetManager, getPhysicsSpace(), rootNode, shootables);
        enemy.setLocation(new Vector3f(10, 6, 0));
        World.enemies.add(enemy);
        
        //terrain
        Material mat_lit = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        TextureKey key3 = new TextureKey("Textures/terrain_grass.jpg");
        Texture tex3 = assetManager.loadTexture(key3);
        tex3.setWrap(WrapMode.Repeat);
        mat_lit.setTexture("DiffuseMap", tex3);
        mat_lit.setBoolean("UseMaterialColors",true); 
        mat_lit.setBoolean("VertexLighting", true );
        mat_lit.setColor("Specular",ColorRGBA.White);
        mat_lit.setColor("Diffuse",ColorRGBA.White);
        mat_lit.setFloat("Shininess", 100f); // [1,128]   
        
        Node node3 = createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0);
        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, 0f, 0f));
        getPhysicsSpace().add(node3);
        Geometry terrain = new Geometry("Terrain", new Box( 100, 0.0001f, 100f));
        terrain.setMaterial(mat_lit);
        rootNode.attachChild(terrain);
        
        Material mat_terrainDirt = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        TextureKey key2 = new TextureKey("Textures/terrain_dirt.jpg");
        Texture tex2 = assetManager.loadTexture(key2);
        tex2.setWrap(WrapMode.Repeat);
        mat_terrainDirt.setTexture("DiffuseMap", tex2);
        mat_terrainDirt.setBoolean("UseMaterialColors",true); 
        mat_terrainDirt.setBoolean("VertexLighting", true );
        mat_terrainDirt.setColor("Specular",ColorRGBA.White);
        mat_terrainDirt.setColor("Diffuse",ColorRGBA.White);
        mat_terrainDirt.setFloat("Shininess", 100f); // [1,128]   
        //walls
        for (int i = 0; i < 10; i++) {
            initWall( 2, 4, 0.24f, 0.48f, 0.80f, 5+i*7, mat_terrainDirt);
        }
        
        //fire
        debris = 
            new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 20);
        Material debris_mat = new Material(assetManager, 
                "Common/MatDefs/Misc/Particle.j3md");
        debris_mat.setTexture("Texture", assetManager.loadTexture(
                "Textures/debris.png"));
        debris.setMaterial(debris_mat);
        debris.setImagesX(2); 
        debris.setImagesY(2); // 3x3 texture animation
        debris.setRotateSpeed(4);
        debris.setSelectRandomImage(true);
        debris.getParticleInfluencer().setVelocityVariation(.2f);
        debris.setStartColor(ColorRGBA.White);
        debris.setGravity(Vector3f.ZERO);
        debris.setLowLife(0.5f);
        debris.setHighLife(1.5f);
        debris.setStartSize(0.14f);
        debris.setEndSize(0.1f);
        rootNode.attachChild(debris);
        debris.setParticlesPerSec(0);
        debris.setShape(new EmitterSphereShape(Vector3f.ZERO, 1));
        
        wind=new Geometry("wind", new Cylinder(2, 10, 1.5f, 1f, 6, false, false));
        wind.setLocalRotation(ROLL090);
        Material mat_wind = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_wind.setTexture("ColorMap", assetManager.loadTexture("Textures/wind.png"));
        mat_wind.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); // activate transparency
        wind.setMaterial(mat_wind);
        wind.setQueueBucket(Bucket.Transparent);
        rootNode.attachChild(wind);
        
        wind_start=new Geometry("wind", new Sphere(3, 14, 3));
        wind_start.setMaterial(mat_wind);
        wind_start.setQueueBucket(Bucket.Transparent);
        rootNode.attachChild(wind_start);
        
        wind_throw = (Geometry) assetManager.loadModel("Models/plane.j3o");
        wind_throw.setLocalScale(80, 20, 1);
        Material mat_wind_throw = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_wind_throw.setTexture("ColorMap", assetManager.loadTexture("Textures/wind_throw.png"));
        mat_wind_throw.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); // activate transparency
        wind_throw.setMaterial(mat_wind_throw);
        wind_throw.setQueueBucket(Bucket.Transparent);
        rootNode.attachChild(wind_throw);
        
        //sky
        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Skysphere.jpg", true));
    }

    private void initLight() {
        /** Must add a light to make the lit object visible! */
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-1,-1,-1).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);
        AmbientLight ambientLight = new AmbientLight();
        ambientLight.setColor(ColorRGBA.White);
        rootNode.addLight(ambientLight);
    }

    /** This loop builds a wall out of individual bricks. */
      public void initWall( int cSize, int rSize, float brickLength, float brickWidth, float brickHeight, float posX, Material wall_mat) {
        Box box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
        float startpt = brickLength / 4;
        float height = 0;
        for (int j = 0; j < rSize; j++) {
          for (int i = 0; i < cSize; i++) {
            Vector3f vt =
             new Vector3f(posX, brickHeight + height, i * brickWidth * 2 - startpt);
            makeBrick(vt, box, wall_mat);
          }
          startpt = -startpt;
          height += 2 * brickHeight;
        }
      }

      /** This method creates one individual physical brick. */
      public void makeBrick(Vector3f loc, Box box, Material wall_mat ) {
        /** Create a brick geometry and attach to scene graph. */
        Geometry brick_geo = new Geometry("brick", box);
        brick_geo.setMaterial(wall_mat);
        shootables.attachChild(brick_geo);
        /** Position the brick geometry  */
        brick_geo.setLocalTranslation(loc);
        /** Make brick physical with a mass > 0.0f. */
        RigidBodyControl brick_phy = new RigidBodyControl(0.01f);
        /** Add physical brick to physics space. */
        brick_geo.addControl(brick_phy);
        bulletAppState.getPhysicsSpace().add(brick_phy);
        
        BoundingSphere bs=new BoundingSphere();
        brick_geo.getMesh().setBound(bs);
        brick_geo.getMesh().updateBound();
      }
    
    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
        World.player.update(tpf);
        for (Enemy enemy : World.enemies) {
            enemy.update(tpf);
        }
        
        //camera follow ballon
        cam.lookAt(World.player.getLocation(), Vector3f.UNIT_Y);
        cam.setLocation(new Vector3f(World.player.getLocation().x, 5, 25));
        
        //wind
        final float windanimationduration=500f;
        if(windanimation==-1){
            wind_start.setCullHint(Spatial.CullHint.Inherit);
            wind_start.rotate( 0, 0, 30*tpf);
            wind.getMaterial().setColor("Color", new ColorRGBA(1f,1f,1f,1-tpf));
        }else if(windanimation<windanimationduration){
            windanimation+=tpf*1000;
            if(windanimation>=windanimationduration) {
                wind.setCullHint(Spatial.CullHint.Always);
                wind_throw.setCullHint(Spatial.CullHint.Always);
                wind_start.setCullHint(Spatial.CullHint.Always);
            }else{
                wind.getMaterial().setColor("Color", new ColorRGBA(1f,1f,1f,(windanimationduration-windanimation)/windanimationduration));
                wind_throw.getMaterial().setColor("Color", new ColorRGBA(1f,1f,1f,(windanimationduration-windanimation)/windanimationduration));
                wind.setCullHint(Spatial.CullHint.Inherit);
                wind_throw.setCullHint(Spatial.CullHint.Inherit);
                wind.rotate(0, 0, 10*tpf);
                wind.setLocalScale(1+windanimation/windanimationduration);
                wind_throw.setLocalScale(80+windanimation/windanimationduration, 20+windanimation/windanimationduration, 1);
            }
        }
        
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
        
    }

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }
    
    /**
     * creates an empty node with a RigidBodyControl
     */
    public static Node createPhysicsTestNode(AssetManager manager, CollisionShape shape, float mass) {
        Node node = new Node("PhysicsNode");
        RigidBodyControl control = new RigidBodyControl(shape, mass);
        node.addControl(control);
        return node;
    }
    
    private static Geometry putShape(Mesh shape,AssetManager assetManager, ColorRGBA color, Node node) {
        Geometry g = new Geometry("coordinate axis", shape);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.getAdditionalRenderState().setWireframe(true);
        mat.setColor("Color", color);
        g.setMaterial(mat);
        node.attachChild(g);
        return g;
    }
    
     private void initKeys() {
        // You can map one or several inputs to one named action
        inputManager.addMapping("Pause",  new KeyTrigger(KeyInput.KEY_P));
        inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));
        inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_L));
        inputManager.addMapping("Up",   new KeyTrigger(KeyInput.KEY_I));
        inputManager.addMapping("Down",  new KeyTrigger(KeyInput.KEY_K));
        inputManager.addMapping("mouse_button_right",  new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        // Add the names to the action listener.
        inputManager.addListener(actionListener, new String[]{"Left", "Right", "Up", "Down", "mouse_button_right" });
        
        inputManager.addMapping("mouse_up", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
        inputManager.addMapping("mouse_down", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
        inputManager.addMapping("mouse_left", new MouseAxisTrigger(MouseInput.AXIS_X, true));
        inputManager.addMapping("mouse_right", new MouseAxisTrigger(MouseInput.AXIS_X, false));
        inputManager.addListener(analogListener, new String[]{"mouse_up", "mouse_down", "mouse_left", "mouse_right" });
      }
     
    private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("mouse_button_right")) {
        pressed=keyPressed;
        if(keyPressed){
            mousePosStart.set(inputManager.getCursorPosition());
            
            Vector2f click2d = mousePosStart;
            Vector3f click3d = cam.getWorldCoordinates(
                new Vector2f(click2d.x, click2d.y), 0f).clone();
            Vector3f dir = cam.getWorldCoordinates(
                new Vector2f(click2d.x, click2d.y), 20f);
            
            
            center.x = -click3d.z * (dir.x-click3d.x)/(dir.z-click3d.z)+click3d.x;
            center.y = -click3d.z * (dir.y-click3d.y) /(dir.z-click3d.z) +click3d.y;
            wind_start.setLocalTranslation(center);
            windanimation=-1;
        }     
        else{
            mousePosEnd.set(inputManager.getCursorPosition());
            mousePosEnd.subtractLocal(mousePosStart);
            final Vector3f normalizeLocal = new Vector3f(mousePosEnd.x, mousePosEnd.y, 0).mult(0.005f);
            
            
            Vector3f spatialCenter=new Vector3f();
            for (Spatial spatial : shootables.getChildren()) {
                RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
                float radius = ((BoundingSphere)spatial.getWorldBound()).getRadius()+1;
                control.getPhysicsLocation(spatialCenter);
                float distance = spatialCenter.distance(center);
                if(distance<radius){
                    control.applyImpulse(normalizeLocal.mult((radius-distance)/radius), center.subtract(spatialCenter));
                }
            }
            
            //Effect
            normalizeLocal.normalizeLocal();
            debris.getParticleInfluencer().setInitialVelocity(normalizeLocal.multLocal(20));
            debris.setLocalTranslation(center);
            debris.emitAllParticles();
            wind.setLocalTranslation(center);
            wind_throw.setLocalTranslation(center);
            wind.lookAt(center.add(normalizeLocal), Vector3f.UNIT_Z);
            wind_throw.lookAt(center.add(normalizeLocal),  Vector3f.UNIT_Z);
            wind_throw.rotate(-FastMath.HALF_PI, -FastMath.HALF_PI, 0);
            windanimation=0;
        } 
      }
    }
  };
    
    private boolean pressed= false;
    private Vector2f mousePosStart= new Vector2f(), mousePosEnd= new Vector2f();
    private Vector3f center = new Vector3f();
    private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {
             
    }
  };
}
